using System;
using System.Collections.Generic;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery.Toolkit
{
    public class CheckQueryContext
    {
        private readonly List<ParamInfo> paramInfoList;
        private readonly ParamTypeChecker paramTypeChecker;
        private readonly Func<string, string> parameterNameFunc;
        private readonly SqlParameterNameGenerator sqlParameterNameGenerator = new SqlParameterNameGenerator();

        internal CheckQueryContext(List<ParamInfo> paramInfoList, ParamTypeChecker paramTypeChecker, Func<string, string> parameterNameFunc)
        {
            this.paramInfoList = paramInfoList;
            this.paramTypeChecker = paramTypeChecker;
            this.parameterNameFunc = parameterNameFunc;
        }

        public IParam<T> CreateParam<T>()
        {
            return new Param<T>(this, GetParamInfo);
        }

        public IParam<ITableValued<T>> CreateTableValuedParam<T>()
        {
            return new Param<ITableValued<T>>(
                this,
                (parameterName, parameterType) =>
                    {
                        var itemTypeName = ControllableQuery.Util.GetSqlTableValuedParameterName<T>();
                        return new ParamInfo(
                            parameterName,
                            paramInfo => itemTypeName.AsOptionalValue(),
                            delegate { return itemTypeName; },
                            parameterType,
                            (sqlParamInfo, queryText, queryType, xmlDocument, xmlNamespaceManager) =>
                            Console.WriteLine("{0} {1} {2}",
                                              sqlParamInfo.ParamInfo.Name,
                                              sqlParamInfo.ParamInfo.ParamType,
                                              sqlParamInfo.SqlType)
                            );
                    }
                );
        }

        private class Param<T> : IParam<T>
        {
            private readonly CheckQueryContext parent;
            private readonly Func<string, Type, ParamInfo> paramInfoFunc;

            public Param(CheckQueryContext parent, Func<string, Type, ParamInfo> paramInfoFunc)
            {
                this.parent = parent;
                this.paramInfoFunc = paramInfoFunc;
            }

            private bool sqlParamIsGenerated;
            public ISqlParam<T> GenerateSqlParam()
            {
                if (sqlParamIsGenerated)
                {
                    throw new QueryCheckException("SqlParam already generated.");
                }

                var result = GetSqlParam(parent.sqlParameterNameGenerator.GetNextName());
                sqlParamIsGenerated = true;
                return result;
            }

            public T Value
            {
                set { throw new InvalidOperationException(); }
            }

            public ISqlParam<T> GetSqlParam(string parameterName)
            {
                return new SqlParam(this, parameterName);
            }

            private class SqlParam : ISqlParam<T>
            {
                private readonly Lazy<string> toSqlResult;

                public SqlParam(Param<T> parent, string parameterName)
                {
                    toSqlResult = new Lazy<string>(
                        () =>
                            {
                                parent.parent.paramInfoList.Add(parent.paramInfoFunc(parameterName, typeof (T)));
                                return parent.parent.parameterNameFunc(parameterName);
                            }
                        );
                }

                public string ToSql()
                {
                    return toSqlResult.Value;
                }

                public override string ToString()
                {
                    return ToSql();
                }
            }

            public override string ToString()
            {
                return this.ToSql();
            }
        }

        private ParamInfo GetParamInfo(string parameterName, Type parameterType)
        {
            return new ParamInfo(
                parameterName,
                delegate { return OptionalValue.Nothing<string>(); },
                (paramInfo, xmlDocument, xmlNamespaceManager) => 
                    paramInfo.GetParamAttributeValue(xmlDocument, xmlNamespaceManager, "DataType"),
                parameterType,
                paramTypeChecker
                );
        }
    }
}